﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BLL.Relations.VectorDistances;
using BLL.Workflows;
using DAL.Clustering;
using DO.Clustering;
using DO.Workflows;

namespace BLL.Clustering
{
    public class ClusteringWorker:WorkerBase 
    {
        #region private fields
        private Dictionary<int, string> _Terms;
        private Dictionary<int, Dictionary<int, FeatureFreq>> _DocFeatFreqs;
        private Dictionary<int, double> _TermWeights;
        private Dictionary<int, Dictionary<int, double>> _DocDocDistances;  // docid->(docid->distance)
        #endregion

        #region Overrides of WorkerBase

        private DocumentClusteringInstruction _Instruction;
        /// <summary>
        /// allow instruction to be changed at runtime
        /// </summary>
        public override InstructionBase Instruction
        {
            get
            {
                return _Instruction;
            }
            set
            {
                _Instruction = (DocumentClusteringInstruction) value;
            }
        }

        private DocumentClusteringOutput _Output;
        /// <summary>
        /// output
        /// </summary>
        public override ResultBase Output
        {
            get
            {
                return _Output;
            }
        }

        /// <summary>
        /// allow worker to be created based on component type
        /// </summary>
        public override Type ComponentType
        {
            get
            {
                return typeof (DocumentClusteringComponent);
            }
        }

        /// <summary>
        /// allow instruction to be instantiated
        /// </summary>
        public override Type InstructionType
        {
            get
            {
                return typeof (DocumentClusteringInstruction);
            }
        }

        public override event WorkerFinished WorkerFinishedEvent;
        public override event WorkerHasError WorkerErredEvent;
        public override event WorkerStatusChanged WorkerStatusChangedEvent;
        protected override void StartWork()
        {
            try
            {
                this._Terms=new Dictionary<int, string>();
                if (this.WorkerStatusChangedEvent != null)
                    this.WorkerStatusChangedEvent(
                        this.JobTicket, this.GetType(),
                        this.Instruction, this.WorkflowName, 
                        this.ComponentName,
                        "Retrieving doc IDs...", 0);
                Dictionary<int, string> docTitles = ClusteringDALC.GetDocIDTitlesFromSQL(
                    this._Instruction.ConnStr, this._Instruction.SQL,
                    this._Instruction.DocIDFieldFromSQL,
                    this._Instruction.DocTitleFieldFromSQL);
                List<int> docIDs=new List<int>();
                docIDs.AddRange(docTitles.Keys);

                if (this.WorkerStatusChangedEvent != null)
                    this.WorkerStatusChangedEvent(
                        this.JobTicket, this.GetType(),
                        this.Instruction, this.WorkflowName,
                        this.ComponentName,
                        "Get indexed terms from DB...", 5);
                Dictionary<int, Dictionary<int, int>> docTermFreqs =
                    ClusteringDALC.GetDocTermFreqs(
                        this._Instruction.ConnStr,
                        this._Instruction.TermDB,
                        this._Instruction.TermTable,
                        this._Instruction.TermIDFieldInTermTable,
                        this._Instruction.PhraseFieldInTermTable,
                        this._Instruction.MappingTable,
                        this._Instruction.DocIDFieldInMappingTable,
                        this._Instruction.TermIDFieldInMappingTable,
                        this._Instruction.TermFreqFieldInMappingTable,
                        docIDs, ref this._Terms);
                this._Output = new DocumentClusteringOutput(
                    this._Instruction.ComponentID, 
                    this.JobTicket,
                    this._Instruction.TaskName,
                    new Dictionary<int, string>(), 
                    new Dictionary<int, List<int>>(), 
                    new Dictionary<int, double>(),
                    new Dictionary<int, Dictionary<int, double>>(),
                    new Dictionary<int, string>(),
                    new Dictionary<int, Dictionary<int, int>>(), 
                    new Dictionary<int, double>(),
                    new Dictionary<int, string>(),
                    new Dictionary<int, Dictionary<int, double>>());
                this._Output.DocTitles = docTitles;
                this._Output.DocTermFreqs = docTermFreqs;
                if (docTermFreqs.Count > 0)
                {
                    // prepare 
                    if (this.WorkerStatusChangedEvent != null)
                        this.WorkerStatusChangedEvent(
                            this.JobTicket, this.GetType(),
                            this.Instruction, this.WorkflowName,
                            this.ComponentName,
                            "Calculating term weights...", 40);
                    this._TermWeights = TermWeightManager.GetTfidfScores(docTermFreqs, this._Terms);
                    this._DocFeatFreqs = TermWeightManager.BuildFeatureFrequencies(docTermFreqs, this._TermWeights);
                    WeightedFeatureSimilarityCalculator calc = new WeightedFeatureSimilarityCalculator();

                    if (this.WorkerStatusChangedEvent != null)
                        this.WorkerStatusChangedEvent(
                            this.JobTicket, this.GetType(),
                            this.Instruction, this.WorkflowName,
                            this.ComponentName,
                            "Calculating document distances...", 60);
                    this._DocDocDistances =
                        DistanceManager.CalculateDocumentDistances(
                            calc, this._DocFeatFreqs);

                    if (this.WorkerStatusChangedEvent != null)
                        this.WorkerStatusChangedEvent(
                            this.JobTicket, this.GetType(),
                            this.Instruction, this.WorkflowName,
                            this.ComponentName,
                            "Clustering documents...", 80);
                    //Dictionary<int, List<int>> clusteredDocs =
                    //    HierarchyClusterManager.BuildClusters(
                    //        this._DocDocDistances,
                    //        this._DocFeatFreqs,
                    //        this._TermWeights);
                    Dictionary<int, List<int>> clusteredDocs =
                        MinSpanTreeClusterManager.BuildClusters(
                            this._DocDocDistances, this._DocFeatFreqs, this._TermWeights);
                    this._Output.ClusteredDocs = clusteredDocs;
                    Dictionary<int,double> clusterDiameters=new Dictionary<int, double>();
                    Dictionary<int,string> clusterLabels=new Dictionary<int, string>();
                    foreach(int clusterID in clusteredDocs.Keys)
                    {
                        double diameter = DistanceManager.GetClusterDiameter(
                            clusteredDocs[clusterID], this._DocDocDistances);
                        clusterDiameters.Add(clusterID, diameter);
                        string label = ClusterLabelManager.GetClusterLabel(
                            clusteredDocs[clusterID], docTermFreqs, this._Terms);
                        clusterLabels.Add(clusterID, label);
                    }
                    this._Output.ClusterDiameters = clusterDiameters;
                    this._Output.ClusterLabels = clusterLabels;
                    this._Output.AllTerms = this._Terms;
                    this._Output.TermWeights = this._TermWeights;
                    this._Output.DocDocDistances = this._DocDocDistances;
                    List<int> topTermIDs = TermWeightManager.GetTopTerms(this._TermWeights, 10);
                    this._Output.TermTermDistances = DistanceManager.CalculateTermTermDistances(
                        calc, topTermIDs, this._DocFeatFreqs);

                    if (this.WorkerStatusChangedEvent != null)
                        this.WorkerStatusChangedEvent(
                            this.JobTicket, this.GetType(),
                            this.Instruction, this.WorkflowName,
                            this.ComponentName,
                            "Saving clustering output...", 60);
                    ClusteringDALC.SaveClusteringOutput(this._Output, this._Instruction.ConnStr,
                        this._Instruction.OutputDB, this._Instruction.ResultTable,
                        this._Instruction.TaskNameField, this._Instruction.DataField);
                }
                else
                    throw new Exception("Unable to get project abstracts");

                if (this.WorkerFinishedEvent != null)
                    this.WorkerFinishedEvent(this.JobTicket, this.GetType(), this.Instruction, this.Output);
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                    this.WorkerErredEvent(this.JobTicket, this.GetType(), this.Instruction, ex.ToString(), true);
            }
        }
        #endregion
    }
}
